package com.mpsc.singleton.demo.util;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.mpsc.singleton.demo.dao.FfmpegVideoInfo;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.ffmpeg.avcodec.AVCodecParameters;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVStream;
import org.bytedeco.ffmpeg.avutil.AVRational;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ws.schild.jave.process.ProcessWrapper;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;

/**
 * @author whgao2
 */
public class FfmpegUtils {

    private static final Logger logger = LoggerFactory.getLogger(FfmpegUtils.class);

    public static void main(String[] args) throws IOException, InterruptedException, JSONException {
        String ffmpegPath = "D:\\ffmpeg\\ffmpeg-2023-07-19\\bin\\ffmpeg.exe";
        String ffProbePath = "D:\\ffmpeg\\ffmpeg-2023-07-19\\bin\\ffprobe.exe";
        String inputFilePath = "D:\\ffmpeg\\潮点网_58515.mp4";
        String outputFilePath = "D:\\ffmpeg\\image";

        //视频切割图片
        // FfmpegUtils.videoToImages(ffmpegPath, ffProbePath, inputFilePath, outputFilePath, "%d.jpg");

        //图片转为视频 ffmpeg -framerate 30 -i image %d.jpg -c:v libx264 -pix_fmt yuv420p output.mp4
        //String inputImagePath = "D:\\ffmpeg\\image\\%d.jpg";
        //String outVideoPath = "D:\\ffmpeg\\image\\video.mp4";
        // FfmpegUtils.imagesToVideo(ffmpegPath, inputImagePath, outVideoPath);
        //FfmpegVideoInfo ffmpegVideoInfo = getFfmpegVideoInfo(ffProbePath, inputFilePath);
        //System.out.println("--------------");
        //getFfmpegVideoInfo("");
        //imageTranscode2("D:\\ffmpeg\\detect_output.mp4", "D:\\ffmpeg\\222_detect_output.mp4");
        //videoIntercept(new File("D:\\ffmpeg\\潮点网_58515.mp4"), "D:\\ffmpeg\\image-2");
        FfmpegVideoInfo info = FfmpegUtils.getFfmpegVideoInfo("D:\\\\ffmpeg\\\\潮点网_58515.mp4");
        //读取文件
        File rootFile = new File("D:\\ffmpeg\\image-2");
        File[] files = rootFile.listFiles();
        List<File> fileList = new ArrayList<>();
        for (int i = 0, len = files.length; i < len; i++) {
            fileList.add(files[i]);
        }
        Collections.sort(fileList, (o1, o2) -> {
            int i1 = Integer.parseInt(o1.getName().substring(0, o1.getName().lastIndexOf(".")));
            int i2 = Integer.parseInt(o2.getName().substring(0, o2.getName().lastIndexOf(".")));
            return i1 - i2;
        });
        boolean b = img2Video(fileList, "D:\\ffmpeg\\image-2\\1.mp4", info.getWidth(), info.getHeight(), info.getAvgFrameRate());
        System.out.println("-------------- b");
    }

    /**
     * 视频转化为图片
     *
     * @param ffmpegPath     ffmeg.exe 二级制文件地址
     * @param ffProbePath    ffprobe.exe 二进制文件地址
     * @param inputVideoPath 输入视频地址
     * @param outputFilePath 输出文件地址
     * @return fileName 文件名称
     */
    public static FfmpegVideoInfo videoToImages(String ffmpegPath, String ffProbePath, String inputVideoPath, String outputFilePath, String fileName) throws JSONException {
        // 检查输出文件或文件夹是否存在
        File outputFile = new File(outputFilePath);
        if (!outputFile.exists()) {
            outputFile.mkdirs();
        }
        FfmpegVideoInfo videoInfo = FfmpegUtils.getFfmpegVideoInfo(ffProbePath, inputVideoPath);
        try {
            if (null != videoInfo.getAvgFrameRate()) {
                FfmpegUtils.executeFFmpegCommand(ffmpegPath, "-i", inputVideoPath, "-vf", String.format("fps=%s", videoInfo.getAvgFrameRate()), outputFilePath + File.separator + fileName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return videoInfo;
    }

    /**
     * 视频转化为图片
     *
     * @param ffmpegPath     ffmeg.exe 二级制文件地址
     * @param inputImagePath 输入图像存储文件
     * @param outputFilePath 输出文件地址
     */
    public static boolean imagesToVideo(String ffmpegPath, String inputImagePath, String outputFilePath) throws JSONException {
        //执行命令将图片转化为视频
        FfmpegUtils.executeFFmpegCommand(ffmpegPath, "-f", "image2", "-i", inputImagePath, outputFilePath);
        return true;
    }


    /**
     * 获取所有的流的信息
     *
     * @param ffProbePath    ffmeg.exe 二级制文件地址
     * @param inputVideoPath 输入文件绝对路径
     * @return JSON
     */
    public static FfmpegVideoInfo getFfmpegVideoInfo(String ffProbePath, String inputVideoPath) throws JSONException {
        FfmpegVideoInfo result = new FfmpegVideoInfo();
        logger.info(ffProbePath + " -v quiet -print_format json -show_format  -show_streams " + inputVideoPath);
        List<String> resultList = FfmpegUtils.executeFFmpegCommand(ffProbePath, "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", inputVideoPath);
        JSONObject videoJson = new JSONObject(String.join("\n", resultList));
        if (videoJson.length() != 0) {
            //获取"codec_type":"video" 的流信息
            JSONArray stream = videoJson.getJSONArray("streams");
            String[] rates;
            for (int i = 0, len = stream.length(); i < len; i++) {
                JSONObject jsonObject = stream.getJSONObject(i);
                if (!"video".equals(jsonObject.getString("codec_type"))) {
                    continue;
                }
                rates = jsonObject.getString("avg_frame_rate").split("/");
                result.setDuration((int) Math.round(jsonObject.getDouble("duration")));
                result.setAvgFrameRate(Math.round(Integer.parseInt(rates[0]) / Integer.parseInt(rates[1])));
                result.setWidth(jsonObject.getInt("width"));
                result.setHeight(jsonObject.getInt("height"));
                result.setCodecType(jsonObject.getString("codec_type"));
            }
        }
        return result;
    }

    /**
     * 获取所有的流的信息
     *
     * @param ffProbePath    ffmeg.exe 二级制文件地址
     * @param inputVideoPath 输入文件绝对路径
     * @return JSON
     */
    public static JSONObject getVideoDetails(String ffProbePath, String inputVideoPath) throws JSONException {
        // 这里会获取所有的流的信息，如果需要指定某个流，输入参数加个int streamIndex,ffprobe指令在quiet后面加上："-select_streams",String.format("v:%d",streamIndex),
        List<String> resultList = FfmpegUtils.executeFFmpegCommand(ffProbePath, "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", inputVideoPath);
        return new JSONObject(String.join("\n", resultList));
    }

    /**
     * 执行 ffmpeg 命令
     *
     * @param ffmpegPath ffmeg.exe 二级制文件地址
     * @param command    命令集合
     */
    public static List<String> executeFFmpegCommand(String ffmpegPath, String... command) throws JSONException {
        List<String> resultList = new ArrayList<>();
        try {
            List<String> fullCommand = new ArrayList<>();
            fullCommand.add(ffmpegPath);
            fullCommand.addAll(Arrays.asList(command));

            ProcessBuilder processBuilder = new ProcessBuilder(fullCommand);
            processBuilder.redirectErrorStream(true);
            Process process = processBuilder.start();
            Thread outputThread = new Thread(() -> {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        resultList.add(line);
                        System.out.println(line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            outputThread.start();
            process.waitFor();
            outputThread.join();
            System.out.println("FFmpeg command executed.");
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }

        return resultList;
    }


    public void sampleFFmpegExecute(String ffmpegPath, String inputVideoPath, String outputFilePath) {
        //FFmpegResponse response = new FFmpegResponse();
        //FFmpegCommandExecutorService executor = new FFmpegCommandExecutorServiceImpl();
        // 检查输出文件是否存在
        File outputFile = new File(outputFilePath);
        if (outputFile.exists()) {
            System.out.println("Output file already exists. Please choose a different name.");
            //return response;
        }
        // 示例1: 转换视频格式
        // executor.executeFFmpegCommand(ffmpegPath, "-i", inputFilePath, outputFilePath);

        // 示例2: 修改视频分辨率
//        executor.executeFFmpegCommand(ffmpegPath, "-i", inputFilePath, "-vf", "scale=1280:720", outputFilePath);

        // 示例3: 剪切视频
        // executor.executeFFmpegCommand(ffmpegPath, "-i", inputFilePath, "-ss", "00:00:10", "-to", "00:00:15", outputFilePath);

        //示例4：将视频中的按每一秒抽取为图片格式
        // executor.executeFFmpegCommand(ffmpegPath,"-i",inputFilePath,"-vf", "fps=1",".\\out\\output_%04d.jpg");

        //示例5：获取视频文件中的帧数信息
        //ffprobe获取的是分数形式的帧率信息
        //executor.executeFFmpegCommand(ffProbePath,"-v", "error", "-select_streams", "v:0", "-show_entries", "stream=r_frame_rate", "-of", "default=noprint_wrappers=1:nokey=1",inputFilePath);
        //ffmpeg获取视频文件信息，包含各种媒体信息，由于没有指定输出文件而报错，但是不影响信息输出
        // executor.executeFFmpegCommand(ffmpegPath,"-i",inputFilePath);

        //示例6：从视频流中获取指定时长的视频
        //这里的视频流来自cctv1
        //response = executor.executeFFmpegCommand(ffmpegPath, "-i", inputVideoPath, "-t", "10", outputFilePath);

        //示例7：从视频流中获取指定帧率参数
//      FFmpegResponse response = executor.executeFFmpegCommand(ffProbePath,"-v", "error", "-select_streams", "v:0", "-show_entries", "stream=r_frame_rate", "-of", "default=noprint_wrappers=1:nokey=1",inputFilePath);
//      System.out.println(response);

        //return response;
    }

    /**
     * 将视频进行转码
     * ffmpeg -i D:\ffmpeg\detect_output.mp4 -vcodec h264 D:\ffmpeg\detect_output11.mp4
     *
     * @param transcode  转码格式
     * @param inputPath  输入视频地址
     * @param outputPath 输出视频地址
     * @return boolean
     * @ffmeg.exe 二级制文件地址
     */
    public static boolean imageTranscode(String ffmpegPath, String transcode, String inputPath, String outputPath) throws JSONException {
        FfmpegUtils.executeFFmpegCommand(ffmpegPath, "-i", inputPath, "-vcodec", transcode, outputPath);
        return true;
    }

    /**
     * 将视频进行转码*
     *
     * @param inputPath  输入视频地址
     * @param outputPath 输出视频地址
     * @return boolean
     * @ffmeg.exe 二级制文件地址
     */
    public static boolean imageTranscode2(String inputPath, String outputPath) throws IOException {
        ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor();
        ffmpeg.addArgument("-i");
        ffmpeg.addArgument(inputPath);
        ffmpeg.addArgument("-c:v");
        ffmpeg.addArgument("libx264");
        ffmpeg.addArgument("-crf");
        ffmpeg.addArgument("19");
        ffmpeg.addArgument("-strict");
        ffmpeg.addArgument("experimental");
        ffmpeg.addArgument(outputPath);
        ffmpeg.execute();
        try (BufferedReader br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
            blockFfmpeg(br);
        }
        File file = new File(outputPath);
        file.createNewFile();
        return true;
    }

    private static void blockFfmpeg(BufferedReader br) throws IOException {
        String line;
        // 该方法阻塞线程，直至合成成功
        while ((line = br.readLine()) != null) {
            doNothing(line);
        }
    }

    private static void doNothing(String line) {
        System.out.println(line);
    }


    /**
     * 获取视频信息数据
     *
     * @param inputFile 输入文件绝对地址
     * @return 返回视频信息数据
     */
    public static FfmpegVideoInfo getFfmpegVideoInfo(String inputFile) throws FFmpegFrameGrabber.Exception {
        FfmpegVideoInfo result = new FfmpegVideoInfo();
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
        grabber.start(true);
        AVFormatContext avformatcontext = grabber.getFormatContext();
        // 取得视频的帧率
        int framerate = (int) grabber.getVideoFrameRate();
        logger.info("视频帧率:{}，视频时长:{}秒", framerate, avformatcontext.duration() / 1000000);
        // 源视频宽度
        int frameWidth = grabber.getImageWidth();
        // 源视频高度
        int frameHeight = grabber.getImageHeight();
        result.setDuration((int) (avformatcontext.duration() / 1000000));
        result.setAvgFrameRate(framerate);
        result.setWidth(frameWidth);
        result.setHeight(frameHeight);
        result.setCodecType(grabber.getVideoCodecName());
        return result;
    }

    /**
     * 视频转分辨率转视频编码
     *
     * @param inputFile     源文件
     * @param outputPath    输出目录
     * @param filterContent 过滤器配置参数
     * @param width         需要转成的视频的宽度
     * @param height        需要转成的视频的高度
     * @param videoFormat   需要转换成的视频格式
     * @return 返回新文件名称，可以根据实际使用修改
     */
    public static String videoConvert(String inputFile, String outputPath, String filterContent, Integer width, Integer height, String videoFormat) {
        List<String> paths = Splitter.on(".").splitToList(inputFile);
        String ext = paths.get(paths.size() - 1);
        if (StringUtils.isEmpty(videoFormat)) {
            videoFormat = ext;
        }
        String newFileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + videoFormat;
        String resultPath = Joiner.on(File.separator).join(Arrays.asList(outputPath, newFileName));

        Frame frame;
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
        FFmpegFrameRecorder recorder = null;
        double aspectRatio = 1.0;

        FFmpegFrameFilter filter = new FFmpegFrameFilter(filterContent, grabber.getImageWidth(), grabber.getImageHeight());
        try {
            // 初始化帧抓取器，例如数据结构（时间戳、编码器上下文、帧对象等），
            // 如果入参等于true，还会调用avformat_find_stream_info方法获取流的信息，放入AVFormatContext类型的成员变量oc中
            grabber.start(true);
            // grabber.start方法中，初始化的解码器信息存在放在grabber的成员变量oc中

            //过滤器启动
            filter.start();

            AVFormatContext avformatcontext = grabber.getFormatContext();
            // 文件内有几个媒体流（一般是视频流+音频流）
            int streamNum = avformatcontext.nb_streams();
            // 没有媒体流就不用继续了
            if (streamNum < 1) {
                logger.info("视频文件格式不对");
                return "error";
            }
            // 取得视频的帧率
            int framerate = (int) grabber.getVideoFrameRate();
            logger.info("视频帧率:{}，视频时长:{}秒，媒体流数量:{}", framerate, avformatcontext.duration() / 1000000,
                    streamNum);
            // 遍历每一个流，检查其类型
            for (int i = 0; i < streamNum; i++) {
                AVStream avstream = avformatcontext.streams(i);
                AVCodecParameters avcodecparameters = avstream.codecpar();
                logger.info("流的索引:{}，编码器类型:{}，编码器ID:{}", i, avcodecparameters.codec_type(),
                        avcodecparameters.codec_id());
            }
            // 源视频宽度
            int frameWidth = grabber.getImageWidth();
            // 源视频高度
            int frameHeight = grabber.getImageHeight();
            // 源音频通道数量
            int audioChannels = grabber.getAudioChannels();
            logger.info("源视频宽度:{}，源视频高度:{}，音频通道数:{}", frameWidth, frameHeight, audioChannels);


            AVFormatContext ifmt_ctx = grabber.getFormatContext();
            AVStream inpVideoStream = null,
                    inpAudioStream = null;
            if (ifmt_ctx != null) {
                //获取Video的AVStream
                inpVideoStream = ifmt_ctx.streams(grabber.getVideoStream());
                //不能使用 inpVideoStream.sample_aspect_ratio()
                AVRational SAR = inpVideoStream.codecpar().sample_aspect_ratio();
                double a = (double) SAR.num() / (double) SAR.den();
                //默认情况为1:1
                aspectRatio = a == 0.0 ? 1.0 : a;
                int SARden = SAR.den();
                int SARnum = SAR.num();
                logger.info("SARden[{}], SARnum[{}]", SARden, SARnum);
            }

            recorder = new FFmpegFrameRecorder(resultPath, width, height, audioChannels);
            // 设置H264编码
//            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            // 设置视频格式
            recorder.setFormat(videoFormat);
            // 使用原始视频的码率，若需要则自行修改码率
            recorder.setVideoBitrate(grabber.getVideoBitrate());
            // 一秒内的帧数，帧率
            recorder.setFrameRate(framerate);
            // 两个关键帧之间的帧数
            recorder.setGopSize(framerate);
            // 设置音频通道数，与视频源的通道数相等
            recorder.setAudioChannels(grabber.getAudioChannels());

            //给录制器设置SAR
            recorder.setAspectRatio(aspectRatio);

            recorder.start();
            int videoFrameNum = 0;
            int audioFrameNum = 0;
            int dataFrameNum = 0;
            // 持续从视频源取帧
            while (null != (frame = grabber.grab())) {
                // 有图像，就把视频帧加一
                if (null != frame.image) {
                    videoFrameNum++;
                    //把已经解码后的视频图像像素塞进过滤器
                    filter.push(frame);
                    //取出过滤器处理后的图像像素数据
                    Frame filterFrame = filter.pullImage();
                    // 取出的每一帧，都保存到视频
                    recorder.record(filterFrame);
                }
                // 有声音，就把音频帧加一
                if (null != frame.samples) {
                    audioFrameNum++;
                    // 取出的每一帧
                    recorder.record(frame);
                }
                // 有数据，就把数据帧加一
                if (null != frame.data) {
                    dataFrameNum++;
                }
            }
            logger.info("转码完成，视频帧:{}，音频帧:{}，数据帧:{}", videoFrameNum, audioFrameNum, dataFrameNum);
        } catch (Exception e) {
            logger.error(e.getMessage());
            return "error";
        } finally {
            if (recorder != null) {
                try {
                    recorder.close();
                } catch (Exception e) {
                    logger.info("recorder.close异常" + e);
                }
            }
            try {
                filter.close();
            } catch (FrameFilter.Exception e) {
                logger.info("filter.close异常" + e);
            }
            try {
                grabber.close();
            } catch (FrameGrabber.Exception e) {
                logger.info("frameGrabber.close异常" + e);
            }


        }
        return newFileName;
    }


    /**
     *  视频转化为图片并报存到指定文件夹
     *   @param inputPath
     *   @param start
     *  @param end
     *  
     */
    public static List<File> videoIntercept(String inputPath, String filePath) {
        Frame frame = null;
        List<File> files = new ArrayList<>();
        FFmpegFrameGrabber fFmpegFrameGrabber = new FFmpegFrameGrabber(new File(inputPath));
        try {
            File splitFile = new File(filePath);
            if (!splitFile.exists()){
                splitFile.mkdirs();
            }
            fFmpegFrameGrabber.start();
            int ftp = fFmpegFrameGrabber.getLengthInFrames();
            System.out.println("开始视频提取帧");
            for (int i = 0; i < ftp; i++) {
                frame = fFmpegFrameGrabber.grabImage();
                doExecuteFrame(frame, filePath, i, files);
            }
            System.out.println("============运行结束============");
            fFmpegFrameGrabber.stop();
        } catch (IOException E) {
            System.out.println(E);
            // Loggers.ERROR.error("视频抽帧异常", e);
        }
        return files;
    }

    /**
     * 将图片写入到指定文件夹下
     *
     * @param frame          Frame
     * @param targetFilePath 文件写入地址
     * @param index          文件前缀
     * @param files          文件集合
     */
    public static void doExecuteFrame(Frame frame, String targetFilePath, int index, List<File> files) {
        if (frame == null || frame.image == null) {
            return;
        }
        Java2DFrameConverter converter = new Java2DFrameConverter();
        String imageMat = "jpg";
        String fileName = targetFilePath + File.separator + index + "." + imageMat;
        BufferedImage bi = converter.getBufferedImage(frame);
        File output = new File(fileName);
        files.add(output);
        try {
            ImageIO.write(bi, imageMat, output);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static boolean img2Video(List<File> fileList,
                                    String outFilePath, int videoWidth, int videoHeight, int frameRate) throws FrameRecorder.Exception {
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outFilePath, videoWidth, videoHeight);
        try {
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
            recorder.setFormat("mp4");
            recorder.setFrameRate(frameRate);
            recorder.start();
            // 加入图片
            Java2DFrameConverter converter = new Java2DFrameConverter();
            for (int i = 0, len = fileList.size(); i < len; i++) {
                BufferedImage read = ImageIO.read(new FileInputStream(fileList.get(i)));
                recorder.record(converter.getFrame(read));
                if (i / 10 == 0){
                    System.out.println("---------------------" + i);
                }
            }
            return true;
        } catch (Exception e) {
            logger.error("javacv：img2Video error", e);
            return false;
        } finally {
            recorder.stop();
            recorder.release();
            recorder.close();
        }
    }
}
